/*
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.awt;

import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.MultipleGradientPaint.ColorSpaceType;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;

/**
 * Provides the actual implementation for the RadialGradientPaint.
 * This is where the pixel processing is done.  A RadialGradienPaint
 * only supports circular gradients, but it should be possible to scale
 * the circle to look approximately elliptical, by means of a
 * gradient transform passed into the RadialGradientPaint constructor.
 *
 * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
 */
final class RadialGradientPaintContext extends MultipleGradientPaintContext {

  /**
   * True when (focus == center).
   */
  private boolean isSimpleFocus = false;

  /**
   * True when (cycleMethod == NO_CYCLE).
   */
  private boolean isNonCyclic = false;

  /**
   * Radius of the outermost circle defining the 100% gradient stop.
   */
  private float radius;

  /**
   * Variables representing center and focus points.
   */
  private float centerX, centerY, focusX, focusY;

  /**
   * Radius of the gradient circle squared.
   */
  private float radiusSq;

  /**
   * Constant part of X, Y user space coordinates.
   */
  private float constA, constB;

  /**
   * Constant second order delta for simple loop.
   */
  private float gDeltaDelta;

  /**
   * This value represents the solution when focusX == X.  It is called
   * trivial because it is easier to calculate than the general case.
   */
  private float trivial;

  /**
   * Amount for offset when clamping focus.
   */
  private static final float SCALEBACK = .99f;

  /**
   * Constructor for RadialGradientPaintContext.
   *
   * @param paint the {@code RadialGradientPaint} from which this context is created
   * @param cm the {@code ColorModel} that receives the {@code Paint} data (this is used only as a
   * hint)
   * @param deviceBounds the device space bounding box of the graphics primitive being rendered
   * @param userBounds the user space bounding box of the graphics primitive being rendered
   * @param t the {@code AffineTransform} from user space into device space (gradientTransform
   * should be concatenated with this)
   * @param hints the hints that the context object uses to choose between rendering alternatives
   * @param cx the center X coordinate in user space of the circle defining the gradient.  The last
   * color of the gradient is mapped to the perimeter of this circle.
   * @param cy the center Y coordinate in user space of the circle defining the gradient.  The last
   * color of the gradient is mapped to the perimeter of this circle.
   * @param r the radius of the circle defining the extents of the color gradient
   * @param fx the X coordinate in user space to which the first color is mapped
   * @param fy the Y coordinate in user space to which the first color is mapped
   * @param fractions the fractions specifying the gradient distribution
   * @param colors the gradient colors
   * @param cycleMethod either NO_CYCLE, REFLECT, or REPEAT
   * @param colorSpace which colorspace to use for interpolation, either SRGB or LINEAR_RGB
   */
  RadialGradientPaintContext(RadialGradientPaint paint,
      ColorModel cm,
      Rectangle deviceBounds,
      Rectangle2D userBounds,
      AffineTransform t,
      RenderingHints hints,
      float cx, float cy,
      float r,
      float fx, float fy,
      float[] fractions,
      Color[] colors,
      CycleMethod cycleMethod,
      ColorSpaceType colorSpace) {
    super(paint, cm, deviceBounds, userBounds, t, hints,
        fractions, colors, cycleMethod, colorSpace);

    // copy some parameters
    centerX = cx;
    centerY = cy;
    focusX = fx;
    focusY = fy;
    radius = r;

    this.isSimpleFocus = (focusX == centerX) && (focusY == centerY);
    this.isNonCyclic = (cycleMethod == CycleMethod.NO_CYCLE);

    // for use in the quadractic equation
    radiusSq = radius * radius;

    float dX = focusX - centerX;
    float dY = focusY - centerY;

    double distSq = (dX * dX) + (dY * dY);

    // test if distance from focus to center is greater than the radius
    if (distSq > radiusSq * SCALEBACK) {
      // clamp focus to radius
      float scalefactor = (float) Math.sqrt(radiusSq * SCALEBACK / distSq);
      dX = dX * scalefactor;
      dY = dY * scalefactor;
      focusX = centerX + dX;
      focusY = centerY + dY;
    }

    // calculate the solution to be used in the case where X == focusX
    // in cyclicCircularGradientFillRaster()
    trivial = (float) Math.sqrt(radiusSq - (dX * dX));

    // constant parts of X, Y user space coordinates
    constA = a02 - centerX;
    constB = a12 - centerY;

    // constant second order delta for simple loop
    gDeltaDelta = 2 * (a00 * a00 + a10 * a10) / radiusSq;
  }

  /**
   * Return a Raster containing the colors generated for the graphics
   * operation.
   *
   * @param x,y,w,h the area in device space for which colors are generated.
   */
  protected void fillRaster(int pixels[], int off, int adjust,
      int x, int y, int w, int h) {
    if (isSimpleFocus && isNonCyclic && isSimpleLookup) {
      simpleNonCyclicFillRaster(pixels, off, adjust, x, y, w, h);
    } else {
      cyclicCircularGradientFillRaster(pixels, off, adjust, x, y, w, h);
    }
  }

  /**
   * This code works in the simplest of cases, where the focus == center
   * point, the gradient is noncyclic, and the gradient lookup method is
   * fast (single array index, no conversion necessary).
   */
  private void simpleNonCyclicFillRaster(int pixels[], int off, int adjust,
      int x, int y, int w, int h) {
        /* We calculate sqrt(X^2 + Y^2) relative to the radius
         * size to get the fraction for the color to use.
         *
         * Each step along the scanline adds (a00, a10) to (X, Y).
         * If we precalculate:
         *   gRel = X^2+Y^2
         * for the start of the row, then for each step we need to
         * calculate:
         *   gRel' = (X+a00)^2 + (Y+a10)^2
         *         = X^2 + 2*X*a00 + a00^2 + Y^2 + 2*Y*a10 + a10^2
         *         = (X^2+Y^2) + 2*(X*a00+Y*a10) + (a00^2+a10^2)
         *         = gRel + 2*(X*a00+Y*a10) + (a00^2+a10^2)
         *         = gRel + 2*DP + SD
         * (where DP = dot product between X,Y and a00,a10
         *  and   SD = dot product square of the delta vector)
         * For the step after that we get:
         *   gRel'' = (X+2*a00)^2 + (Y+2*a10)^2
         *          = X^2 + 4*X*a00 + 4*a00^2 + Y^2 + 4*Y*a10 + 4*a10^2
         *          = (X^2+Y^2) + 4*(X*a00+Y*a10) + 4*(a00^2+a10^2)
         *          = gRel  + 4*DP + 4*SD
         *          = gRel' + 2*DP + 3*SD
         * The increment changed by:
         *     (gRel'' - gRel') - (gRel' - gRel)
         *   = (2*DP + 3*SD) - (2*DP + SD)
         *   = 2*SD
         * Note that this value depends only on the (inverse of the)
         * transformation matrix and so is a constant for the loop.
         * To make this all relative to the unit circle, we need to
         * divide all values as follows:
         *   [XY] /= radius
         *   gRel /= radiusSq
         *   DP   /= radiusSq
         *   SD   /= radiusSq
         */
    // coordinates of UL corner in "user space" relative to center
    float rowX = (a00 * x) + (a01 * y) + constA;
    float rowY = (a10 * x) + (a11 * y) + constB;

    // second order delta calculated in constructor
    float gDeltaDelta = this.gDeltaDelta;

    // adjust is (scan-w) of pixels array, we need (scan)
    adjust += w;

    // rgb of the 1.0 color used when the distance exceeds gradient radius
    int rgbclip = gradient[fastGradientArraySize];

    for (int j = 0; j < h; j++) {
      // these values depend on the coordinates of the start of the row
      float gRel = (rowX * rowX + rowY * rowY) / radiusSq;
      float gDelta = (2 * (a00 * rowX + a10 * rowY) / radiusSq +
          gDeltaDelta / 2);

            /* Use optimized loops for any cases where gRel >= 1.
             * We do not need to calculate sqrt(gRel) for these
             * values since sqrt(N>=1) == (M>=1).
             * Note that gRel follows a parabola which can only be < 1
             * for a small region around the center on each scanline. In
             * particular:
             *   gDeltaDelta is always positive
             *   gDelta is <0 until it crosses the midpoint, then >0
             * To the left and right of that region, it will always be
             * >=1 out to infinity, so we can process the line in 3
             * regions:
             *   out to the left  - quick fill until gRel < 1, updating gRel
             *   in the heart     - slow fraction=sqrt fill while gRel < 1
             *   out to the right - quick fill rest of scanline, ignore gRel
             */
      int i = 0;
      // Quick fill for "out to the left"
      while (i < w && gRel >= 1.0f) {
        pixels[off + i] = rgbclip;
        gRel += gDelta;
        gDelta += gDeltaDelta;
        i++;
      }
      // Slow fill for "in the heart"
      while (i < w && gRel < 1.0f) {
        int gIndex;

        if (gRel <= 0) {
          gIndex = 0;
        } else {
          float fIndex = gRel * SQRT_LUT_SIZE;
          int iIndex = (int) (fIndex);
          float s0 = sqrtLut[iIndex];
          float s1 = sqrtLut[iIndex + 1] - s0;
          fIndex = s0 + (fIndex - iIndex) * s1;
          gIndex = (int) (fIndex * fastGradientArraySize);
        }

        // store the color at this point
        pixels[off + i] = gradient[gIndex];

        // incremental calculation
        gRel += gDelta;
        gDelta += gDeltaDelta;
        i++;
      }
      // Quick fill to end of line for "out to the right"
      while (i < w) {
        pixels[off + i] = rgbclip;
        i++;
      }

      off += adjust;
      rowX += a01;
      rowY += a11;
    }
  }

  // SQRT_LUT_SIZE must be a power of 2 for the test above to work.
  private static final int SQRT_LUT_SIZE = (1 << 11);
  private static float sqrtLut[] = new float[SQRT_LUT_SIZE + 1];

  static {
    for (int i = 0; i < sqrtLut.length; i++) {
      sqrtLut[i] = (float) Math.sqrt(i / ((float) SQRT_LUT_SIZE));
    }
  }

  /**
   * Fill the raster, cycling the gradient colors when a point falls outside
   * of the perimeter of the 100% stop circle.
   *
   * This calculation first computes the intersection point of the line
   * from the focus through the current point in the raster, and the
   * perimeter of the gradient circle.
   *
   * Then it determines the percentage distance of the current point along
   * that line (focus is 0%, perimeter is 100%).
   *
   * Equation of a circle centered at (a,b) with radius r:
   * (x-a)^2 + (y-b)^2 = r^2
   * Equation of a line with slope m and y-intercept b:
   * y = mx + b
   * Replacing y in the circle equation and solving using the quadratic
   * formula produces the following set of equations.  Constant factors have
   * been extracted out of the inner loop.
   */
  private void cyclicCircularGradientFillRaster(int pixels[], int off,
      int adjust,
      int x, int y,
      int w, int h) {
    // constant part of the C factor of the quadratic equation
    final double constC =
        -radiusSq + (centerX * centerX) + (centerY * centerY);

    // coefficients of the quadratic equation (Ax^2 + Bx + C = 0)
    double A, B, C;

    // slope and y-intercept of the focus-perimeter line
    double slope, yintcpt;

    // intersection with circle X,Y coordinate
    double solutionX, solutionY;

    // constant parts of X, Y coordinates
    final float constX = (a00 * x) + (a01 * y) + a02;
    final float constY = (a10 * x) + (a11 * y) + a12;

    // constants in inner loop quadratic formula
    final float precalc2 = 2 * centerY;
    final float precalc3 = -2 * centerX;

    // value between 0 and 1 specifying position in the gradient
    float g;

    // determinant of quadratic formula (should always be > 0)
    float det;

    // sq distance from the current point to focus
    float currentToFocusSq;

    // sq distance from the intersect point to focus
    float intersectToFocusSq;

    // temp variables for change in X,Y squared
    float deltaXSq, deltaYSq;

    // used to index pixels array
    int indexer = off;

    // incremental index change for pixels array
    int pixInc = w + adjust;

    // for every row
    for (int j = 0; j < h; j++) {

      // user space point; these are constant from column to column
      float X = (a01 * j) + constX;
      float Y = (a11 * j) + constY;

      // for every column (inner loop begins here)
      for (int i = 0; i < w; i++) {

        if (X == focusX) {
          // special case to avoid divide by zero
          solutionX = focusX;
          solutionY = centerY;
          solutionY += (Y > focusY) ? trivial : -trivial;
        } else {
          // slope and y-intercept of the focus-perimeter line
          slope = (Y - focusY) / (X - focusX);
          yintcpt = Y - (slope * X);

          // use the quadratic formula to calculate the
          // intersection point
          A = (slope * slope) + 1;
          B = precalc3 + (-2 * slope * (centerY - yintcpt));
          C = constC + (yintcpt * (yintcpt - precalc2));

          det = (float) Math.sqrt((B * B) - (4 * A * C));
          solutionX = -B;

          // choose the positive or negative root depending
          // on where the X coord lies with respect to the focus
          solutionX += (X < focusX) ? -det : det;
          solutionX = solutionX / (2 * A); // divisor
          solutionY = (slope * solutionX) + yintcpt;
        }

        // Calculate the square of the distance from the current point
        // to the focus and the square of the distance from the
        // intersection point to the focus. Want the squares so we can
        // do 1 square root after division instead of 2 before.

        deltaXSq = X - focusX;
        deltaXSq = deltaXSq * deltaXSq;

        deltaYSq = Y - focusY;
        deltaYSq = deltaYSq * deltaYSq;

        currentToFocusSq = deltaXSq + deltaYSq;

        deltaXSq = (float) solutionX - focusX;
        deltaXSq = deltaXSq * deltaXSq;

        deltaYSq = (float) solutionY - focusY;
        deltaYSq = deltaYSq * deltaYSq;

        intersectToFocusSq = deltaXSq + deltaYSq;

        // get the percentage (0-1) of the current point along the
        // focus-circumference line
        g = (float) Math.sqrt(currentToFocusSq / intersectToFocusSq);

        // store the color at this point
        pixels[indexer + i] = indexIntoGradientsArrays(g);

        // incremental change in X, Y
        X += a00;
        Y += a10;
      } //end inner loop

      indexer += pixInc;
    } //end outer loop
  }
}
